1 /**
2  * Primitive aspects for an image
3  *
4  * This module is a submodule of devisualization.image.
5  * 
6  * Provides determinance upon if a type is an image and respective optional functionality.
7  * 
8  * License:
9  *              Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017.
10  *     Distributed under the Boost Software License, Version 1.0.
11  *        (See accompanying file LICENSE_1_0.txt or copy at
12  *              http://www.boost.org/LICENSE_1_0.txt)
13  */
14 module devisualization.image.primitives;
15 import devisualization.image.interfaces;
16 import std.experimental.color;
17 import devisualization.util.core.memory.managed : managed;
18 import std.traits : isPointer, PointerTarget;
19 import stdx.allocator : IAllocator, theAllocator;
20 
21 /**
22  * Determine if a type is an image.
23  * 
24  * Use ImageStorage as the definition to compare against.
25  *
26  * See_Also:
27  *      ImageStorage
28  */
29 bool isImage(Image)() pure if (isPointer!Image && !__traits(compiles, {alias T = Image.PayLoadType;})) {
30     return isImage!(PointerTarget!Image)();
31 }
32 
33 ///
34 bool isImage(Image)() pure if (__traits(compiles, {alias T = Image.PayLoadType;})) {
35 	return isImage!(Image.PayLoadType)();
36 }
37 
38 ///
39 bool isImage(Image)() pure if (!isPointer!Image && !__traits(compiles, {alias T = Image.PayLoadType;})) {
40 	import std.traits : ReturnType, isIntegral, isUnsigned, Parameters;
41     import std.experimental.color : isColor;
42 
43 	static if (__traits(compiles, {Image image = Image.init;}) && is(Image == class) || is(Image == interface) || is(Image == struct)) {
44         // check that we can get the color type
45         static if (!__traits(compiles, {alias Color = ReturnType!(Image.getPixel);}))
46             return false;
47         else {
48             // make the color type easily worked on
49             alias Color = ReturnType!(Image.getPixel);
50 
51 			static if (!isColor!Color)
52                 return false;
53             else {
54 				static if (!__traits(compiles, {
55                     Image image = void;
56                     Color c = image.getPixel(0, 0);
57                     image.setPixel(0, 0, c);
58                     c = image[0, 0];
59                     image[0, 0] = c;
60 
61                     bool didResize = image.resize(2, 2);
62                 })) {
63                     // mutation, is it possible?
64 					return false;
65 				} else static if(!(isIntegral!(ReturnType!(Image.width)) && isUnsigned!(ReturnType!(Image.width)) && is(ReturnType!(Image.width) == ReturnType!(Image.height)))) {
66 					// length must be unsigned integer (of some kind)
67 					return false;
68                 } else {
69 					foreach(overload; __traits(getOverloads, Image, "getPixel")) {
70 						alias Targs = Parameters!overload;
71 
72 						if (!(Targs.length == 2 && is(Targs[0] == Targs[1]) && isIntegral!(Targs[0])))
73 							return false;
74 					}
75 
76                     // ok, is an image
77                     return true;
78                 }
79             }
80         }
81     } else {
82 		return false;
83     }
84 }
85 
86 version(unittest) private {
87     /*
88      * Tests isImage with a class that inherits from Image storage.
89      * Confirming that it works with that interface.
90      */
91 
92     class MyImageStorageUnit(Color) : ImageStorage!Color {
93         private {
94             const size_t width_, height_;
95             IAllocator allocator;
96             Color[][] data;
97         }
98 
99         this(size_t width, size_t height, IAllocator allocator = theAllocator()) {
100             import stdx.allocator : makeArray;
101             this.allocator = allocator;
102 
103             width_ = width;
104             height_ = height;
105             
106             data = allocator.makeArray!(Color[])(width);
107             
108             foreach(_; 0 .. width) {
109                 data[_] = allocator.makeArray!Color(height);
110             }
111         }
112 
113         @property {
114             size_t width() @nogc nothrow @trusted { return width_; }
115             size_t height() @nogc nothrow @trusted { return height_; }
116         }
117 
118         Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; }
119         void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; }
120         Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); }
121         void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); }
122 
123         bool resize(size_t, size_t) @nogc @safe { return false; }
124     }
125 
126     struct NotAColor {}
127 
128     // implicitly checks if it is an image
129 
130     static assert(!__traits(compiles, { alias Type = MyImageStorageUnit!NotAColor; }));
131     static assert(__traits(compiles, { alias Type = MyImageStorageUnit!RGB8; }));
132     static assert(isImage!(MyImageStorageUnit!RGB8));
133 
134     // while we are at it, lets just test that the image color is gettable.
135     static assert(is(ImageColor!(MyImageStorageUnit!RGB8) == RGB8));
136 
137     /*
138      * Check if the class still works if it does not inherit from ImageStorage interface.
139      */
140 
141     class MyImageStorageUnitNoInheritance(Color) {
142         private {
143             const size_t width_, height_;
144             IAllocator allocator;
145             Color[][] data;
146         }
147 
148         this(size_t width, size_t height, IAllocator allocator = theAllocator()) {
149             import stdx.allocator : makeArray;
150             this.allocator = allocator;
151 
152             width_ = width;
153             height_ = height;
154             
155             data = allocator.makeArray!(Color[])(width);
156             
157             foreach(_; 0 .. width) {
158                 data[_] = allocator.makeArray!Color(height);
159             }
160         }
161 
162         @property {
163             size_t width() @nogc nothrow @trusted { return width_; }
164             size_t height() @nogc nothrow @trusted { return height_; }
165         }
166 
167         Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; }
168         void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; }
169         Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); }
170         void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); }
171 
172         bool resize(size_t, size_t) @nogc @safe { return false; }
173     }
174 
175     // no implcit checks for if it is an image
176 
177     static assert(!isImage!(MyImageStorageUnitNoInheritance!NotAColor));
178     static assert(isImage!(MyImageStorageUnitNoInheritance!RGB8));
179 
180     // while we are at it, lets just test that the image color is gettable.
181     static assert(is(ImageColor!(MyImageStorageUnitNoInheritance!RGB8) == RGB8));
182 
183     /*
184      * Check if as a struct it still works.
185      */
186 
187     struct MyImageStorageUnitStruct(Color) {
188         private {
189             const size_t width_, height_;
190             IAllocator allocator;
191             Color[][] data;
192         }
193 
194         this(size_t width, size_t height, IAllocator allocator = theAllocator()) {
195             import stdx.allocator : makeArray;
196             this.allocator = allocator;
197 
198             width_ = width;
199             height_ = height;
200             
201             data = allocator.makeArray!(Color[])(width);
202             
203             foreach(_; 0 .. width) {
204                 data[_] = allocator.makeArray!Color(height);
205             }
206         }
207 
208         @property {
209             size_t width() @nogc nothrow @trusted { return width_; }
210             size_t height() @nogc nothrow @trusted { return height_; }
211         }
212 
213         Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; }
214         void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; }
215         Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); }
216         void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); }
217 
218         bool resize(size_t, size_t) @nogc @safe { return false; }
219     }
220 
221     // no implcit checks for if it is an image
222 
223     static assert(!isImage!(MyImageStorageUnitStruct!NotAColor));
224     static assert(isImage!(MyImageStorageUnitStruct!RGB8));
225 
226     // while we are at it, lets just test that the image color is gettable.
227     static assert(is(ImageColor!(MyImageStorageUnitStruct!RGB8) == RGB8));
228 }
229 
230 /**
231  * The color type that an image storage type complies with.
232  */
233 template ImageColor(Image) if (isImage!Image) {
234     import std.traits : ReturnType;
235     alias ImageColor = ReturnType!(Image.getPixel);
236 }
237 
238 /**
239  * The type utilized as the main index for an image.
240  * 
241  * Uses the first overload.
242  */
243 template ImageIndexType(Image) if (isImage!Image) {
244     import std.traits : Parameters, isPointer;
245 
246     // https://issues.dlang.org/show_bug.cgi?id=19170
247     static if (isPointer!Image) {
248         static assert(!isPointer!(typeof(Image.init[0])));
249         alias ImageIndexType = Parameters!(__traits(getOverloads, typeof(Image.init[0]), "getPixel")[0])[0];
250     } else {
251         alias ImageIndexType = Parameters!(__traits(getOverloads, Image, "getPixel")[0])[0];
252     }
253 }
254 
255 /**
256  * Determine if an image supports the pixel offset extension.
257  * 
258  * Use ImageStorageOffset as the definition to compare against.
259  *
260  * See_Also:
261  *      ImageStorage, ImageStorageOffset
262  */
263 bool supportsImageOffset(Image)() if (isImage!Image) {
264     import std.traits : ReturnType;
265 
266     static if (is(Image == class) || is(Image == struct)) {
267         Image image;
268 
269         // make the color type easily worked on
270 
271         alias Color = ReturnType!(Image.getPixel);
272 
273         if (!__traits(compiles, {
274             size_t count = image.count;
275         })) {
276             // can we get access to basic elements of the image?            
277             return false;
278         } else if (!__traits(compiles, {
279             Color c = image.getPixelAtOffset(0);
280             image.setPixelAtOffset(0, c);
281             c = image[0];
282             image[0] = c;
283         })) {
284             // mutation, is it possible?
285             return false;
286         } else {
287             // ok, supports pixel offset
288             return true;
289         }
290     } else
291         return false;
292 }
293 
294 version(unittest) private {
295     /*
296      * Tests supportsImageOffset with a class that inherits from Image storage.
297      * Confirming that it works with that interface.
298      */
299 
300     class MyImageStorageUnit3(Color) : ImageStorage!Color, ImageStorageOffset!Color {
301         private {
302             const size_t width_, height_;
303             IAllocator allocator;
304             Color[][] data;
305         }
306 
307         this(size_t width, size_t height, IAllocator allocator = theAllocator()) {
308             import stdx.allocator : makeArray;
309             this.allocator = allocator;
310 
311             width_ = width;
312             height_ = height;
313             
314             data = allocator.makeArray!(Color[])(width);
315             
316             foreach(_; 0 .. width) {
317                 data[_] = allocator.makeArray!Color(height);
318             }
319         }
320 
321         @property {
322             size_t width() @nogc nothrow @trusted { return width_; }
323             size_t height() @nogc nothrow @trusted { return height_; }
324             size_t count() @nogc nothrow @trusted { return width_ * height_; }
325         }
326 
327         Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; }
328         void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; }
329         Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); }
330         void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); }
331 
332         Color getPixelAtOffset(size_t offset) @nogc @trusted { return getPixel(offset % height_, offset / height_);}
333         void setPixelAtOffset(size_t offset, Color value) @nogc @trusted { setPixel(offset % height_, offset / height_, value); }
334         Color opIndex(size_t offset) @nogc @trusted { return getPixelAtOffset(offset); }
335         void opIndexAssign(Color value, size_t offset) @nogc @trusted { setPixelAtOffset(offset, value); }
336 
337         bool resize(size_t, size_t) @nogc @safe { return false; }
338     }
339 
340     // implicitly checks if it is an image
341 
342     static assert(__traits(compiles, { alias Type = MyImageStorageUnit3!RGB8; }));
343     static assert(isImage!(MyImageStorageUnit3!RGB8));
344     static assert(supportsImageOffset!(MyImageStorageUnit3!RGB8));
345 
346     /*
347      * Check if the class still works if it does not inherit from ImageStorage interface.
348      */
349 
350     class MyImageStorageUnit2NoInheritance(Color) {
351         private {
352             const size_t width_, height_;
353             IAllocator allocator;
354             Color[][] data;
355         }
356 
357         this(size_t width, size_t height, IAllocator allocator = theAllocator()) {
358             import stdx.allocator : makeArray;
359             this.allocator = allocator;
360 
361             width_ = width;
362             height_ = height;
363             
364             data = allocator.makeArray!(Color[])(width);
365             
366             foreach(_; 0 .. width) {
367                 data[_] = allocator.makeArray!Color(height);
368             }
369         }
370 
371         @property {
372             size_t width() @nogc nothrow @trusted { return width_; }
373             size_t height() @nogc nothrow @trusted { return height_; }
374             size_t count() @nogc nothrow @trusted { return width_ * height_; }
375         }
376 
377         Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; }
378         void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; }
379         Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); }
380         void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); }
381 
382         Color getPixelAtOffset(size_t offset) @nogc @trusted { return getPixel(offset % height_, offset / height_);}
383         void setPixelAtOffset(size_t offset, Color value) @nogc @trusted { setPixel(offset % height_, offset / height_, value); }
384         Color opIndex(size_t offset) @nogc @trusted { return getPixelAtOffset(offset); }
385         void opIndexAssign(Color value, size_t offset) @nogc @trusted { setPixelAtOffset(offset, value); }
386 
387         bool resize(size_t, size_t) @nogc @safe { return false; }
388     }
389 
390     // no implcit checks for if it is an image
391 
392     static assert(__traits(compiles, { alias Type = MyImageStorageUnit2NoInheritance!RGB8; }));
393     static assert(isImage!(MyImageStorageUnit2NoInheritance!RGB8));
394     static assert(supportsImageOffset!(MyImageStorageUnit2NoInheritance!RGB8));
395 
396     /*
397      * Check if as a struct it still works.
398      */
399 
400     struct MyImageStorageUnit2Struct(Color) {
401         private {
402             const size_t width_, height_;
403             IAllocator allocator;
404             Color[][] data;
405         }
406 
407         this(size_t width, size_t height, IAllocator allocator = theAllocator()) {
408             import stdx.allocator : makeArray;
409             this.allocator = allocator;
410 
411             width_ = width;
412             height_ = height;
413             
414             data = allocator.makeArray!(Color[])(width);
415             
416             foreach(_; 0 .. width) {
417                 data[_] = allocator.makeArray!Color(height);
418             }
419         }
420 
421         @property {
422             size_t width() @nogc nothrow @trusted { return width_; }
423             size_t height() @nogc nothrow @trusted { return height_; }
424             size_t count() @nogc nothrow @trusted { return width_ * height_; }
425         }
426 
427         Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; }
428         void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; }
429         Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); }
430         void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); }
431 
432         Color getPixelAtOffset(size_t offset) @nogc @trusted { return getPixel(offset % height_, offset / height_);}
433         void setPixelAtOffset(size_t offset, Color value) @nogc @trusted { setPixel(offset % height_, offset / height_, value); }
434         Color opIndex(size_t offset) @nogc @trusted { return getPixelAtOffset(offset); }
435         void opIndexAssign(Color value, size_t offset) @nogc @trusted { setPixelAtOffset(offset, value); }
436 
437         bool resize(size_t, size_t) @nogc @safe { return false; }
438     }
439 
440     // no implcit checks for if it is an image
441 
442     static assert(__traits(compiles, { alias Type = MyImageStorageUnit2Struct!RGB8; }));
443     static assert(isImage!(MyImageStorageUnit2Struct!RGB8));
444     static assert(supportsImageOffset!(MyImageStorageUnit2Struct!RGB8));
445 }
446 
447 /**
448  * Is the type an input range that uses a PixelPoint as element type.
449  *
450  * See_Also:
451  *      PixelPoint
452  */
453 bool isPixelRange(T)() pure {
454     import std.range.interfaces : isInputRange, ElementType;
455 
456     static if (isInputRange!T) {
457         alias ET = ElementType!T;
458         return __traits(hasMember, ET, "value") && is(ET == PixelPoint!(typeof(ET.value))) && isColor!(typeof(ET.value));
459     } else
460         return false;
461 }
462 
463 /// The pixel input range color type
464 template PixelRangeColor(T) if (isPixelRange!T) {
465     alias PixelColor = typeof((ElementType!T).value);
466     static assert(isColor!PixelColor);
467 }
468 
469 /**
470  * Copies an image into another image
471  *
472  * Params:
473  *      input       =   The input image
474  *      destination =   The output image
475  *
476  * Returns:
477  *      The destination image for composibility reasons
478  */
479 Image2 copyTo(Image1, Image2)(ref Image1 input, Image2 destination) @trusted if (is(Image1 == struct) && !is(Image2 == struct) && !isPointer!Image1 && isImage!Image1 && isImage!Image2)  {
480 	return copyTo(&input, destination);
481 }
482 
483 /// Ditto
484 ref Image2 copyTo(Image1, Image2)(Image1 input, ref Image2 destination) @trusted if (is(Image2 == struct) && !isPointer!Image2 && isImage!Image1 && isImage!Image2)  {
485 	return *copyTo(input, &destination);
486 }
487 
488 /// Ditto
489 Image2 copyTo(Image1, Image2)(Image1 input, Image2 destination) /+@nogc+/ @safe if (isImage!Image1 && isImage!Image2) {
490     assert(input.width <= destination.width);
491     assert(input.height <= destination.height);
492     
493     foreach(x; 0 .. destination.width) {
494         foreach(y; 0 .. destination.height) {
495             destination.setPixel(x, y, input.getPixel(x, y));
496         }
497     }
498     
499     return destination;
500 }
501 
502 /**
503  * Copies an pixel image range into an image
504  *
505  * Auto converts the color to the destination one.
506  *
507  * Params:
508  *      input       =   The pixel input range
509  *      destination =   The output image
510  *
511  * Returns:
512  *      The destination image for composibility reasons
513  */
514 Image copyInto(IRRange, Image)(ref IRRange input, ref Image destination) @nogc @safe if (isPixelRange!IRRange && isImage!Image) {
515     alias Color = PixelRangeColor!IRRange;
516     
517     foreach(pixel; input) {
518 		destination.setPixel(pixel.x, pixel.y, pixel.value.convertTo!Color2);
519 	}
520 
521     return destination;
522 }
523 
524 /**
525  * Copies an pixel image range into an image
526  *
527  * Params:
528  *      input       =   The pixel input range
529  *      destination =   The output image
530  *
531  * Returns:
532  *      The destination image for composibility reasons
533  */
534 Image copyInto(IRRange, Image)(ref IRRange input, ref Image destination) @nogc @safe if (isPixelRange!IRRange && isImage!Image && is(ImageColor!Image == PixelRangeColor!(PixelRangeColor!IRRange))) {
535     foreach(pixel; input) {
536 		destination.setPixel(pixel.x, pixel.y, pixel.value);
537 	}
538 
539     return destination;
540 }
541 
542 /**
543  * Copies an pixel image range into a new image
544  * 
545  * Params:
546  *      input       = The pixel input range
547  *      destination = The output image that will be created, determines type to create as
548  *      allocator   = The allocator to allocate the new image by
549  * 
550  * Returns:
551  *      The pixel input range for composibility reasons
552  */
553 IR createImageFrom(ImageImpl, IR)(IR input, out ImageImpl destination, IAllocator allocator=theAllocator()) @safe if (isImage!ImageImpl && isPixelRange!IR) {
554     import stdx.allocator : make;
555 
556     size_t width = input.front.width;
557     size_t height = input.front.height;
558 
559     destination = allocator.make!ImageImpl(width, height, allocator);
560 
561     return input;
562 }
563 
564 /**
565  * Copies an input ranges buffer into an image, ahead of time assigns all pixels to a specific color
566  * 
567  * Params:
568  *      input       = The pixel input range
569  *      destination = The output image that will be created, determines type to create as
570  *      fillAs      = The color to assign to each pixel
571  * 
572  * See_Also:
573  *      createImageFrom, copyInto, fillOn
574  * 
575  * Returns:
576  *      The destination image for composibility reasons
577  */
578 Image assignTo(IR, Image, Color)(IR input, ref Image destination, Color fillAs) @nogc @safe if (isImage!ImageImpl && isPixelRange!IR && isColor!Color) {
579     return input.copyInto(destination.fillOn(fillAs));
580 }
581 
582 // TODO: unittests for copyInto, isPixelRange, PixelRangeColor, createImageFrom, assignTo